package crypto

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha1"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"math/rand"
	"net/url"
	"sort"
	"strings"
	"time"
	"unsafe"

	"gitee.com/quantum-force/of-utils/pkg/crypto"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

const (
	letterBytes   = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	letterIdxBits = 6                    // 6 bits to represent a letter index
	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

// RandString 随机字符串
func RandString(n int) string {
	b := make([]byte, n)
	for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
		if remain == 0 {
			cache, remain = rand.Int63(), letterIdxMax
		}
		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache >>= letterIdxBits
		remain--
	}

	return *(*string)(unsafe.Pointer(&b))
}

// DecryptMessage 解密消息
func DecryptMessage(appID, encrypt, token, aesKey, signature, timestamp, nonce string) (rawXML []byte, err error) {
	h := sha1.New()
	keys := []string{token, timestamp, nonce, encrypt}
	sort.Strings(keys)
	_, _ = io.WriteString(h, strings.Join(keys, ""))
	s := hex.EncodeToString(h.Sum(nil))
	if signature != s {
		return nil, fmt.Errorf("签名校验错误")
	}
	var encryptedBytes, key, plaintext []byte
	encryptedBytes, err = base64.StdEncoding.DecodeString(encrypt)
	if err != nil {
		return
	}
	key, err = aesKeyDecode(aesKey)
	if err != nil {
		return
	}
	plaintext, err = crypto.AesDecrypt(encryptedBytes, key)
	if err != nil {
		return nil, err
	}
	rawXMLMsgLen := int(decodeNetworkByteOrder(plaintext[16:20]))
	appIDOffset := 20 + rawXMLMsgLen
	appIDBytes := plaintext[appIDOffset:]
	if string(appIDBytes) != appID {
		return nil, fmt.Errorf("消息解密校验APPID失败")
	}
	return plaintext[20:appIDOffset:appIDOffset], nil
}

const xmlTemplate = `<xml>
<Encrypt><![CDATA[%s]]></Encrypt>
<MsgSignature><![CDATA[%s]]></MsgSignature>
<TimeStamp>%s</TimeStamp>
<Nonce><![CDATA[%s]]></Nonce>
</xml>`

// EncryptMsg 加密消息
func EncryptMsg(appID, msg, token, aesKey, timestamp, nonce string) (xmlString string, err error) {
	var key []byte
	key, err = aesKeyDecode(aesKey)
	if err != nil {
		return "", err
	}
	ciphertext, err := AESEncryptMsg(msg, appID, key)
	if err != nil {
		return "", err
	}
	encrypt := base64.StdEncoding.EncodeToString(ciphertext)
	msgSignature := Signature(token, timestamp, nonce, encrypt)
	return fmt.Sprintf(xmlTemplate, encrypt, msgSignature, timestamp, nonce), nil
}

// AESEncryptMsg ciphertext = AES_Encrypt[random(16B) + msg_len(4B) + rawXMLMsg + appId]
//参考：github.com/chanxuehong/wechat.v2
func AESEncryptMsg(msg, appID string, aesKey []byte) (ciphertext []byte, err error) {
	const (
		BlockSize = 32            // PKCS#7
		BlockMask = BlockSize - 1 // BLOCK_SIZE 为 2^n 时, 可以用 mask 获取针对 BLOCK_SIZE 的余数
	)

	appIDOffset := 20 + len(msg)
	contentLen := appIDOffset + len(appID)
	amountToPad := BlockSize - contentLen&BlockMask
	plaintextLen := contentLen + amountToPad

	plaintext := make([]byte, plaintextLen)

	// 拼接
	copy(plaintext[:16], RandString(16))
	encodeNetworkByteOrder(plaintext[16:20], uint32(len(msg)))
	copy(plaintext[20:], msg)
	copy(plaintext[appIDOffset:], appID)

	// PKCS#7 补位
	for i := contentLen; i < plaintextLen; i++ {
		plaintext[i] = byte(amountToPad)
	}

	// 加密
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}
	mode := cipher.NewCBCEncrypter(block, aesKey[:16])
	mode.CryptBlocks(plaintext, plaintext)

	ciphertext = plaintext
	return
}

//Signature sha1签名
func Signature(params ...string) string {
	sort.Strings(params)
	h := sha1.New()
	for _, s := range params {
		_, _ = io.WriteString(h, s)
	}
	return fmt.Sprintf("%x", h.Sum(nil))
}

// JsSDKSign 参数签名
func JsSDKSign(values url.Values) string {
	var buf strings.Builder
	keys := make([]string, 0, len(values))
	for k := range values {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		if k == "sign" {
			continue
		}
		vs := values[k]
		for _, v := range vs {
			if v == "" {
				continue
			}
			if buf.Len() > 0 {
				buf.WriteByte('&')
			}
			buf.WriteString(k)
			buf.WriteByte('=')
			buf.WriteString(v)
		}
	}
	valuesStr := buf.String()

	h := sha1.New()
	h.Write([]byte(valuesStr))

	return fmt.Sprintf("%x", h.Sum(nil))
}

// 把整数 n 格式化成 4 字节的网络字节序
func encodeNetworkByteOrder(orderBytes []byte, n uint32) {
	orderBytes[0] = byte(n >> 24)
	orderBytes[1] = byte(n >> 16)
	orderBytes[2] = byte(n >> 8)
	orderBytes[3] = byte(n)
}

// 从 4 字节的网络字节序里解析出整数
func decodeNetworkByteOrder(orderBytes []byte) (n uint32) {
	return uint32(orderBytes[0])<<24 |
		uint32(orderBytes[1])<<16 |
		uint32(orderBytes[2])<<8 |
		uint32(orderBytes[3])
}

func aesKeyDecode(encodedAESKey string) (key []byte, err error) {
	if len(encodedAESKey) != 43 {
		err = fmt.Errorf("the length of encodedAESKey must be equal to 43")
		return
	}
	key, err = base64.StdEncoding.DecodeString(encodedAESKey + "=")
	if err != nil {
		return
	}
	if len(key) != 32 {
		err = fmt.Errorf("encodingAESKey invalid")
		return
	}
	return
}
